import type {
  FetchOperationConfig,
  FetchOperationHeaders,
} from './processors/fetch_operator'
import type { IframeOperationConfig } from './processors/iframe_operator'
import { logger } from '../../types/constants'
import { ToolKit } from '../utils/toolkit'
import { FetchOperator } from './processors/fetch_operator'
import { IframeOperator } from './processors/iframe_operator'

export enum DocumentFetchMethod {
  FETCH = 'fetch',
  IFRAME = 'iframe',
  AJAX = 'ajax',
}

/**
 * 文档获取配置
 */
export interface DocumentFetchConfig {
  url: string
  method?: DocumentFetchMethod // 获取方式
  timeout?: number
  retryCount?: number
  retryDelay?: number
  encoding?: string // 强制编码
  fallbackMethods?: DocumentFetchMethod[] // 备用方法
  detectEncoding?: boolean // 是否自动检测编码
  fixGarbledText?: boolean // 是否修复乱码
  // Fetch 特定配置
  fetchHeaders?: FetchOperationHeaders
  fetchCredentials?: RequestCredentials
  fetchMode?: RequestMode
}

/**
 * 文档获取结果
 */
export interface DocumentFetchResult<T> {
  success: boolean
  url: string // 请求的 URL
  encoding: string // 实际使用的编码
  data?: T
  error?: Error
  methodUsed: string // 实际使用的方法
  duration: number
  retryCount: number
  encodingDetected?: string
  garbledFixed?: boolean
  statusCode?: number // HTTP 状态码
  metadata?: any
}

export interface DocumentResultWithStatus {
  doc: Document
  statusCode?: number
}

const DEFAULT_DOCUMENT_FETCH_CONFIG: Required<DocumentFetchConfig> = {
  url: '',
  method: DocumentFetchMethod.FETCH,
  encoding: '',
  timeout: 8000,
  retryCount: 2,
  retryDelay: 1000,
  detectEncoding: true,
  fixGarbledText: false,
  fallbackMethods: [DocumentFetchMethod.AJAX, DocumentFetchMethod.IFRAME],
  fetchHeaders: {},
  fetchCredentials: 'same-origin',
  fetchMode: 'cors',
}

/**
 * 智能文档获取器
 */
export class DocumentOperator {
  private readonly name: string = 'DocumentOperator'
  private static instance: DocumentOperator
  private readonly fetchOperator: FetchOperator = FetchOperator.getInstance()
  private readonly iframeOperator: IframeOperator = IframeOperator.getInstance()
  private readonly charset: string = ToolKit.ui.page.getCharsetFromMetaTags()

  public static getInstance(): DocumentOperator {
    if (!DocumentOperator.instance) {
      DocumentOperator.instance = new DocumentOperator()
    }
    return DocumentOperator.instance
  }

  /**
   * 获取文档
   */
  public async fetchDocument(config: DocumentFetchConfig): Promise<DocumentFetchResult<Document>> {
    const startTime = Date.now()
    const mergedConfig = ToolKit.config.mergeConfig(DEFAULT_DOCUMENT_FETCH_CONFIG, config)
    const { url, method, retryCount: maxRetries, retryDelay, fallbackMethods, encoding, fixGarbledText } = mergedConfig
    const shortenUrl = ToolKit.browser.url.shortenUrl(url)

    let retryCount = 0
    let lastError: Error | undefined
    let lastStatusCode: number | undefined

    while (retryCount <= maxRetries) {
      try {
        // 尝试主要方法
        const result = await this.tryFetchWithMethod(mergedConfig, method)

        if (result.success && result.data) {
          const duration = Date.now() - startTime

          // 检查并修复乱码
          const finalDoc = fixGarbledText !== false
            ? this.fixGarbledText(result.data)
            : result.data

          const garbledFixed = finalDoc !== result.data

          logger.debug(`[${this.name}] 文档获取成功`, {
            url: ToolKit.browser.url.shortenUrl(url),
            method,
            duration: `${duration}ms`,
            retryCount,
            garbledFixed,
            statusCode: result.statusCode,
          })

          return {
            success: true,
            url,
            encoding,
            data: finalDoc,
            methodUsed: method,
            duration,
            retryCount,
            garbledFixed,
            encodingDetected: this.charset,
            statusCode: result.statusCode,
          }
        }
        else {
          throw result.error || new Error(`文档获取失败：${url}`)
        }
      }
      catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error))

        // 提取状态码（如果可用）
        if (error instanceof Error && error.message.includes('HTTP')) {
          const statusMatch = error.message.match(/HTTP (\d+)/)
          if (statusMatch) {
            lastStatusCode = Number.parseInt(statusMatch[1])
          }
        }

        retryCount++

        if (retryCount <= maxRetries) {
          logger.warn(`[${this.name}] 获取失败，准备重试`, {
            url: shortenUrl,
            method,
            retryCount,
            maxRetries,
            error: lastError.message,
            statusCode: lastStatusCode,
          })

          await ToolKit.async.delay(retryDelay)
        }
      }
    }

    // 主要方法失败，尝试备用方法
    if (fallbackMethods.length > 0) {
      logger.info(`[${this.name}] 主要方法失败，尝试备用方法`, {
        url: shortenUrl,
        fallbackMethods,
      })

      for (const fallbackMethod of fallbackMethods) {
        try {
          const result = await this.tryFetchWithMethod(mergedConfig, fallbackMethod)

          if (result.success && result.data) {
            const duration = Date.now() - startTime
            const finalDoc = fixGarbledText !== false
              ? this.fixGarbledText(result.data)
              : result.data

            logger.info(`[${this.name}] 备用方法获取成功`, {
              url: shortenUrl,
              method: fallbackMethod,
              duration: `${duration}ms`,
              statusCode: result.statusCode,
            })

            return {
              success: true,
              url,
              encoding,
              data: finalDoc,
              methodUsed: fallbackMethod,
              duration,
              retryCount: maxRetries,
              garbledFixed: finalDoc !== result.data,
              encodingDetected: this.charset,
              statusCode: result.statusCode,
            }
          }
        }
        catch (error) {
          logger.warn(`[${this.name}] 备用方法失败`, {
            url: shortenUrl,
            method: fallbackMethod,
            error: error instanceof Error ? error.message : String(error),
          })
        }
      }
    }

    const duration = Date.now() - startTime
    logger.error(`[${this.name}] 所有方法都失败`, {
      url: shortenUrl,
      duration: `${duration}ms`,
      retryCount: maxRetries,
      error: lastError?.message,
      statusCode: lastStatusCode,
    })

    return {
      success: false,
      url,
      encoding,
      error: lastError || new Error('所有获取方法都失败'),
      methodUsed: method,
      duration,
      retryCount: maxRetries,
      encodingDetected: this.charset,
      statusCode: lastStatusCode,
    }
  }

  /**
   * 尝试使用指定方法获取文档
   */
  private async tryFetchWithMethod(
    config: DocumentFetchConfig,
    method: DocumentFetchMethod,
  ): Promise<{ success: boolean, data?: Document, error?: Error, statusCode?: number }> {
    try {
      let doc: Document
      let statusCode: number | undefined

      switch (method) {
        case DocumentFetchMethod.FETCH: {
          const fetchResult = await this.getWithFetch(config)
          doc = fetchResult.doc
          statusCode = fetchResult.statusCode
          break
        }
        case DocumentFetchMethod.AJAX: {
          const ajaxResult = await this.getWithAjax(config)
          doc = ajaxResult.doc
          statusCode = ajaxResult.statusCode
          break
        }
        case DocumentFetchMethod.IFRAME: { const iframeResult = await this.getWithIframe(config)
          doc = iframeResult.doc
          statusCode = iframeResult.statusCode
          break
        }
        default:
          throw new Error(`不支持的获取方法: ${method}`)
      }

      return { success: true, data: doc, statusCode }
    }
    catch (error) {
      let statusCode: number | undefined

      if (error instanceof Error && error.message.includes('HTTP')) {
        const statusMatch = error.message.match(/HTTP (\d+)/)
        if (statusMatch) {
          statusCode = Number.parseInt(statusMatch[1])
        }
      }

      return {
        success: false,
        error: error instanceof Error ? error : new Error(String(error)),
        statusCode,
      }
    }
  }

  /**
   * 使用 Fetch 获取文档
   */
  private async getWithFetch(config: DocumentFetchConfig): Promise<DocumentResultWithStatus> {
    const { url, encoding, detectEncoding, timeout, fetchHeaders, fetchCredentials, fetchMode } = config
    const fetchConfig: FetchOperationConfig = {
      url,
      method: 'GET',
      encoding,
      detectEncoding,
      timeout,
      retryCount: 0, // 在外部处理重试
      headers: {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Charset': encoding || 'utf-8',
        ...fetchHeaders,
      },
      credentials: fetchCredentials,
      mode: fetchMode,
    }

    const result = await this.fetchOperator.fetchHtmlDocument(url, fetchConfig)

    if (!result.success || !result.data) {
      throw result.error || new Error('Fetch 获取失败')
    }

    return {
      doc: result.data,
      statusCode: result.status,
    }
  }

  /**
   * 使用 Ajax 获取文档
   */
  private async getWithAjax(config: DocumentFetchConfig): Promise<DocumentResultWithStatus> {
    const { url, timeout, encoding } = config

    return new Promise((resolve, reject) => {
      $.ajax({
        url,
        dataType: 'text',
        timeout,
        headers: {
          Accept: `text/html; charset=utf-8`,
        },
        beforeSend: (xhr) => {
          // 【关键点】强制指定响应编码
          xhr.overrideMimeType(`text/html; charset=${encoding}`)
        },
        success: (html: string, _textStatus, xhr) => {
          try {
            const doc = this.parseHtmlToDocument(html)
            resolve({
              doc,
              statusCode: xhr.status,
            })
          }
          catch (error) {
            reject(error)
          }
        },
        error: (xhr, status, error) => {
          const statusCode = xhr.status

          if (statusCode >= 400) {
            reject(new Error(`HTTP ${statusCode}: ${error}`))
          }
          else {
            reject(new Error(`Ajax 请求失败: ${status} - ${error}`))
          }
        },
      })
    })
  }

  /**
   * 使用 Iframe 获取文档
   */
  private async getWithIframe(config: DocumentFetchConfig): Promise<DocumentResultWithStatus> {
    const { url, timeout, retryDelay } = config
    const iframeConfig: IframeOperationConfig = {
      url,
      timeout,
      retryCount: 0, // 在外部处理重试
      retryDelay,
    }

    const result = await this.iframeOperator.executeOperation(
      iframeConfig,
      async ($iframeDoc) => {
        return $iframeDoc[0]
      },
      'fetchDocument',
    )

    if (result.success && result.data) {
      return {
        doc: result.data,
        statusCode: 200, // iframe 成功加载通常表示 200
      }
    }
    else {
      throw result.error || new Error('iframe 操作失败')
    }
  }

  /**
   * 解析 HTML 字符串为 Document 对象
   */
  private parseHtmlToDocument(html: string): Document {
    const parser = new DOMParser()
    return parser.parseFromString(html, 'text/html')
  }

  /**
   * 修复文档中的乱码文本
   */
  private fixGarbledText(doc: Document): Document {
    try {
      const textNodes = this.getAllTextNodes(doc.body)
      let fixedCount = 0

      textNodes.forEach((node) => {
        const originalText = node.textContent || ''
        if (this.containsGarbledText(originalText)) {
          const fixedText = this.fixTextEncoding(originalText)
          if (fixedText !== originalText) {
            node.textContent = fixedText
            fixedCount++
          }
        }
      })

      if (fixedCount > 0) {
        logger.debug(`[${this.name}] 修复了 ${fixedCount} 处乱码文本`)
      }

      return doc
    }
    catch (error) {
      logger.warn(`[${this.name}] 乱码修复失败:`, error)
      return doc
    }
  }

  /**
   * 获取所有文本节点
   */
  private getAllTextNodes(element: Element): Text[] {
    const textNodes: Text[] = []
    const walker = document.createTreeWalker(
      element,
      NodeFilter.SHOW_TEXT,
      null,
    )

    let node = walker.nextNode()
    while (node !== null) {
      textNodes.push(node as Text)
      node = walker.nextNode()
    }

    return textNodes
  }

  /**
   * 修复文本编码
   */
  private fixTextEncoding(text: string): string {
    const encodings = ['gbk', 'gb2312', 'big5'] as const

    for (const encoding of encodings) {
      try {
        const fixed = this.convertEncoding(text, encoding, 'utf-8')
        if (!this.containsGarbledText(fixed)) {
          return fixed
        }
      }
      // eslint-disable-next-line unused-imports/no-unused-vars
      catch (error) {
        continue
      }
    }

    return text
  }

  /**
   * 转换编码
   */
  private convertEncoding(text: string, from: string, to: string): string {
    try {
      const encoder = new TextEncoder()
      const decoder = new TextDecoder(from)
      const buffer = encoder.encode(text)
      return decoder.decode(buffer)
    }
    catch (error) {
      logger.warn(`[${this.name}] 编码转换失败:`, { from, to, error })
      return text
    }
  }

  /**
   * 检测是否包含乱码文本
   */
  private containsGarbledText(text: string): boolean {
    const garbledPatterns = [
      /����/,
      /[\uFFFD\uFFFE\uFFFF]/,
      /[^\u0020-\uFFFF]/,
      /^�+$/,
    ]
    return garbledPatterns.some(pattern => pattern.test(text))
  }
}
